Skip to content

IoT-Safe: serialize APDU transactions with a port mutex - #11246

Open
danielinux wants to merge 3 commits into
wolfSSL:masterfrom
danielinux:iotsafe-mutex
Open

IoT-Safe: serialize APDU transactions with a port mutex#11246
danielinux wants to merge 3 commits into
wolfSSL:masterfrom
danielinux:iotsafe-mutex

Conversation

@danielinux

@danielinux danielinux commented Aug 24, 2026

Copy link
Copy Markdown
Member

Description

All IoT-Safe operations share the file-scope static command/response buffers on a single CSIM channel. Concurrent operations (e.g. a random generation and an ECDSA sign callback from different TLS sessions) could overwrite each other's buffers and consume the wrong modem reply, corrupting cryptographic output or leaving the applet in a persistent error state.

Add a port-level mutex (active in multi-threaded builds, no-op when SINGLE_THREADED) held across each APDU transaction. The seven single-transaction operations get thin serialized wrappers. GetRandom locks after its lazy-init check, and the ECDH callback locks its APDU branch, calling the locked impls directly since it already holds the mutex (the software fallback never takes the lock). The mutex is created in iotsafe_init(); document the single-threaded init contract in the doxygen of wolfSSL_CTX_iotsafe_enable() and the CSIM callback setters.

Fixes F-10045.

Testing

Tested via existing iotsafe demos build

Checklist

  • added tests
  • updated/added doxygen
  • updated appropriate READMEs
  • Updated manual and documentation

All IoT-Safe operations share the file-scope static command/response
buffers on a single CSIM channel. Concurrent operations (e.g. a random
generation and an ECDSA sign callback from different TLS sessions)
could overwrite each other's buffers and consume the wrong modem
reply, corrupting cryptographic output or leaving the applet in a
persistent error state.

Add a port-level mutex (active in multi-threaded builds, no-op when
SINGLE_THREADED) held across each APDU transaction. The seven
single-transaction operations get thin serialized wrappers. GetRandom
locks after its lazy-init check, and the ECDH callback locks its APDU
branch, calling the locked impls directly since it already holds the
mutex (the software fallback never takes the lock). The mutex is
created in iotsafe_init(); document the single-threaded init contract
in the doxygen of wolfSSL_CTX_iotsafe_enable() and the CSIM callback
setters.

Fixes F-10045.
Copilot AI lite review requested due to automatic review settings August 24, 2026 09:42
@danielinux danielinux self-assigned this Aug 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses concurrency hazards in the IoT-Safe port by serializing APDU transactions that share file-scope command/response buffers on a single CSIM channel, preventing concurrent TLS sessions from corrupting modem/appet state and cryptographic outputs.

Changes:

  • Introduces a port-level mutex and lock helpers to serialize APDU transactions in multi-threaded builds (no-op under SINGLE_THREADED).
  • Wraps single-transaction IoT-Safe operations with thin “serialized entry point” functions and updates the ECDH callback to hold the lock across its APDU sequence.
  • Updates Doxygen to document the single-threaded initialization contract and concurrent-use guarantees after initialization.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
wolfcrypt/src/port/iotsafe/iotsafe.c Adds mutex-based serialization for IoT-Safe APDU transactions and refactors operations into locked + serialized wrappers.
doc/dox_comments/header_files/iotsafe.h Documents initialization/threading requirements for IoT-Safe enablement and CSIM callback setters.
Suppressed comments (1)

wolfcrypt/src/port/iotsafe/iotsafe.c:101

  • iotsafe_unlock() should mirror iotsafe_lock()'s initialization guard; otherwise callers that hit an early-return/no-op lock path can still attempt to unlock an uninitialized mutex in multi-threaded builds.
static void iotsafe_unlock(void)
{
#if !defined(SINGLE_THREADED)
    wc_UnLockMutex(&iotsafe_mutex);
#endif

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread wolfcrypt/src/port/iotsafe/iotsafe.c
Comment thread wolfcrypt/src/port/iotsafe/iotsafe.c
Comment thread doc/dox_comments/header_files/iotsafe.h Outdated
- iotsafe_init(): free the mutex on the ATE0 and drain failure paths
  so a retried init never double-initializes it
- guard every exported API entry (wc_iotsafe_*, wolfIoTSafe_GetCert_ex,
  wolfIoTSafe_GetRandom) against locking an uninitialized mutex via
  iotsafe_ensure_init(), which lazy-initializes the port before locking
- doxygen: wolfIoTSafe_SetCSIM_write_cb param rf -> wf

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread wolfcrypt/src/port/iotsafe/iotsafe.c
wolfIoT_ecc_shared_secret(): ensure the port is initialized before the
APDU branch can lock iotsafe_mutex. The check sits before tmpKey
allocation (no leak on the early WC_HW_E return) and is gated on
iotsafe->enabled so the software fallback path never forces init.
@wolfSSL-Bot

Copy link
Copy Markdown

Can one of the admins verify this patch?

@danielinux

Copy link
Copy Markdown
Member Author

jenkins retest this please

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #11246

Scan targets checked: wolfcrypt-port-bugs, wolfcrypt-rs-bugs
Findings: 4
2 finding(s) posted as inline comments (see file-level comments below)

Required changes (2)

GEN_KEYPAIR and READ_KEY are not serialized as one operation in wolfIoT_ecc_keygen

File: wolfcrypt/src/port/iotsafe/iotsafe.c:1217
Function: wolfIoT_ecc_keygen
Category: Race conditions

iotsafe_gen_keypair() and iotsafe_get_public_key() each acquire and release iotsafe_mutex separately. A concurrent session regenerating the same ecdh_keypair_slot between the two calls makes the exported public key inconsistent with the applet's stored private key, breaking the handshake. Adjacent to known finding #45, which concerns the shared static buffers rather than applet slot state.

Related known finding #10045 (similar but distinct): Both are concurrent IoT-Safe operation races, but #10045 concerns interleaving access to process-global APDU command/response buffers. This candidate concerns interleaving two individually serialized APDU operations that mutate and read the same applet key slot. It requires an operation-level lock spanning key generation and public-key retrieval, not merely the buffer-serialization patch.

Recommendation: Hold iotsafe_lock() across both steps and call the _locked variants, as wolfIoT_ecc_shared_secret() already does.

Referenced code: wolfcrypt/src/port/iotsafe/iotsafe.c:1217-1221 (5 lines)


PutPublic and VerifyHash are not serialized as one operation in wolfIoT_ecc_verify

File: wolfcrypt/src/port/iotsafe/iotsafe.c:1392
Function: wolfIoT_ecc_verify
Category: Race conditions

iotsafe_put_public_key() and iotsafe_verify_hash() take iotsafe_mutex independently, so another thread storing into the same peer key slot in between leaves the verification running against a different public key. Adjacent to known finding #45, which concerns the shared static buffers rather than applet slot state.

Related known finding #10045 (similar but distinct): Both are races in the IoT-Safe backend, but #10045 is shared host-side APDU buffer corruption between operations. This candidate is an applet-slot time-of-check/use race between separately locked PutPublic and VerifyHash calls. The affected operation sequence, state, and required transaction-level locking patch are distinct.

Recommendation: Hold iotsafe_lock() across the store-then-verify sequence and call the _locked variants.

Referenced code: wolfcrypt/src/port/iotsafe/iotsafe.c:1392-1395 (4 lines)


This review was generated automatically by Fenrir. Reported findings require changes before merge.

* Returns 0 on success, < 0 if initialization failed. */
static int iotsafe_ensure_init(void)
{
if (!wolfIoT_initialized)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lazy mutex creation in iotsafe_ensure_init() is itself racy · Race conditions

The check of wolfIoT_initialized and the following iotsafe_init() (which runs wc_InitMutex(&iotsafe_mutex)) are unsynchronized. Two threads whose first IoT-Safe call races both initialize the same static mutex, overwriting the handle and silently defeating the serialization this PR adds.

Related known finding #10045 (similar but distinct): Both affect iotsafe.c concurrency and involve unsynchronized initialization-related state, but #10045 races shared APDU buffers during operations, whereas this candidate races creation of the newly added mutex itself. The faulting operations, root causes, and fixes differ: transaction serialization does not make mutex initialization safe.

Fix: Create iotsafe_mutex once during library initialization (or use a statically initialized mutex) rather than lazily from every API entry.

* Returns 0 on success, < 0 if initialization failed. */
static int iotsafe_ensure_init(void)
{
if (!wolfIoT_initialized)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lazy init added at every entry point is itself unserialized and can re-initialize a held mutex · Logic errors

iotsafe_ensure_init() runs iotsafe_init() unserialized, and wolfIoT_initialized is only set after the applet-load APDUs finish, so a concurrent first call re-runs wc_InitMutex() on a mutex another thread already holds and drives APDUs over the shared csim_cmd buffer.

Related known finding #10045 (similar but distinct): Both involve concurrent IoT-Safe use and the unsynchronized initialization state, but this finding faults in iotsafe_ensure_init re-running mutex initialization during first-use races; #10045 faults in concurrent APDU transactions overwriting shared command/response buffers. The operations and required initialization-versus-transaction serialization patches differ.

Fix: Use a statically initialized mutex (WOLFSSL_MUTEX_INITIALIZER) to guard init, or drop the lazy init and return WC_HW_E when uninitialized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants